package com.wingstudio.springcloud.controller.aspect;

import com.alibaba.fastjson.JSONObject;
import com.google.common.collect.Lists;
import com.wingstudio.springcloud.annotation.RedisLock;
import com.wingstudio.springcloud.redis.RedisService;
import org.apache.commons.lang.ObjectUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.stereotype.Component;

import java.util.UUID;
import java.util.stream.Collectors;

/**
 * @Author ITcz
 * @Data 2021-04-25 - 22:12
 */
@Component
@Aspect
public class RedisLockAspect {

    private static Logger logger = LoggerFactory.getLogger(RedisLockAspect.class);

    private static final String REDIS_SET_SUCCESS = "OK";

    @Autowired
    private RedisService redisService;

    @Around("@annotation(RedisLock)")
    public Object lockAround(ProceedingJoinPoint joinPoint, RedisLock RedisLock) throws Throwable {
        //SpEL ExpressionParser 接口用于解析表达式字符串
        ExpressionParser parser = new SpelExpressionParser();
        //用于获取方法参数定义名字
        LocalVariableTableParameterNameDiscoverer discoverer = new LocalVariableTableParameterNameDiscoverer();
        //EvaluationContext 接口定义了获取属性、方法、域字段解析器及类型转换器
        //它的实现类 StandardEvaluationContext 使用的是反射机制来操作对象
        EvaluationContext context = new StandardEvaluationContext();

        //通过joinPoint获取被注解方法的形参
        Object[] args = joinPoint.getArgs();
        //通过joinPoint获取被注解方法
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        //LocalVariableTableParameterNameDiscoverer获取方法形参名数组
        String[] params = discoverer.getParameterNames(signature.getMethod());
        //给上下文赋值
        for (int len = 0; len < params.length; len++) {
            context.setVariable(params[len], args[len]);
        }
        //表达式从上下文中计算出实际参数值
        /*如:
            @annotation(key="#student.name")
             method(Student student)
             那么就可以解析出方法形参的某属性值，return "xiaoming";
          */
        Expression expression = parser.parseExpression(RedisLock.keyId());
        String lockKey = expression.getValue(context, String.class);
        //加锁时间最短为10秒
        long lockTime = RedisLock.lockLeaseTime() > 10 ? RedisLock.lockLeaseTime() : 10;
        //尝试获取锁的时间默认为0.3s
        double waitTime = RedisLock.timeout() > 0 ? RedisLock.timeout() : 0;

        String  prefixKey = RedisLock.prefixKey();
        if ("".equals(prefixKey)) {
            //如果没有定义prefixKey，则使用方法名作为prefixKey
            prefixKey = ((MethodSignature) joinPoint.getSignature()).getMethod().getName();
        }
        //使用UUID作为requestId，如果并发量特别高可以使用雪花算法
        String requestId = UUID.randomUUID().toString();
        long startTime = System.currentTimeMillis();
        long endTime = System.currentTimeMillis() + (long) waitTime * 1000;
        //尝试去获取锁
        try {
            do {
                if (redisService.lock(prefixKey, lockKey, requestId, lockTime)) {
                    if (logger.isInfoEnabled()) {
                        logger.info("获得锁成功,方法名为{},参数为{}", joinPoint.getSignature(),
                                String.join("-", Lists.newArrayList(args).stream().map(obj -> JSONObject.toJSONString(ObjectUtils.defaultIfNull(obj, "null")))
                                        .collect(Collectors.toList())));
                    }
                    //获取锁成功了，执行拦截方法，即进入controller
                    Object returnObject = joinPoint.proceed(args);
                    return returnObject;
                }
                //线程最长睡眠0.1s
                int sleepTime = Math.min(100, (int) waitTime * 100);
                if (logger.isDebugEnabled()) {
                    logger.debug("当前无法获得锁,本次等待{}ms,方法名为{},参数为{}", sleepTime, joinPoint.getSignature(),
                            String.join("-", Lists.newArrayList(args).stream().map(obj -> JSONObject.toJSONString(ObjectUtils.defaultIfNull(obj, "null")))
                                    .collect(Collectors.toList())));
                }
                Thread.sleep(sleepTime);
            } while (System.currentTimeMillis() <= endTime);
            if (logger.isInfoEnabled()) {
                logger.info("获得锁失败,放弃等待,之前共等待{}ms,方法将不执行,方法名为{},参数为{}", System.currentTimeMillis() - startTime, joinPoint.getSignature()
                        , String.join("-", Lists.newArrayList(args).stream().map(Object::toString)
                                .collect(Collectors.toList())));
            }
            //获取锁失败，不执行目标方法
            return null;
        } finally {
            //尝试释放锁
            redisService.releaseDistributedLock(prefixKey, lockKey, requestId);
        }
    }


}
